//
// Copyright (c) 2014, 保留所有权利。
//
// CardID.java
//
// 作    者：王红燕
// 当前版本：1.0
// 2014 下午6:45:01
//
// 
//
package com.vanke.agent.common;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 身份证号码工具类，主要实现位数互转，校验，籍贯，生日，性别；15位身份证无法校验<br>
 * 
 * <ul>
 * <li>1.将前17位数分别乘以不同的系数。从第1位到第17位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2</li>
 * <li>2.将这17位数字和系数相乘的结果相加。</li>
 * <li>3.用加出来和除以11，看余数是多少</li>
 * <li>4.余数只可能有0 1 2 3 4 5 6 7 8 9 10。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
 * <li>5.通过上面得知如果余数是2，就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10，身份证的最后一位号码就是2。</li>
 * </ul>
 * 例如：我们要看看这个身份证是不是合法的身份证。 首先：我们得出，前17位的乘积和sum 然后：用sum除以11得出的结果是189 % 11 = mod,
 * 也就是余数 最后：通过对应规则就可以知道余数2对应的数字是x。所以，这是一个合格的身份证号码
 * 
 */
public class CardID {
	private boolean valid = false;

	private String len18 = null;
	private String len15 = null;

	private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
	/**
	 * 性别，1男，2女，0未知
	 */
	private int sex = 0;

	/**
	 * 生日
	 */
	private Date birth = null;

	/**
	 * 籍贯，即出生地行政代码
	 */
	private String nativePlace = "";

	// private String nationalityName = "";

	public CardID(String cardID) {
		if (cardID == null) {
			return;
		}
		Pattern pattern = Pattern.compile("\\d{15}|\\d{17}[x,X,0-9]");
		Matcher matcher = pattern.matcher(cardID);
		if (!matcher.matches()) {
			return;
		}
		init(cardID);
	} //

	private void init(String cardID) {
		if (!validCardID(cardID)) {
			return;
		}
		// 前6位行政编码，表示籍贯
		nativePlace = len18.substring(0, 6);

		// 第17位性别，奇数男，偶数女
		int gender = Character.digit(len18.charAt(16), 10);
		sex = gender % 2 == 0 ? 2 : 1;

		try {
			// 生日
			birth = sdf.parse(cardID.substring(6, 14));
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 校验是否是一个有效的身份证。如果是18的身份证，则按18位算法校验；15位无法校验。
	 */
	private boolean validCardID(String cardId) {
		StringBuilder sbId = new StringBuilder(cardId.toUpperCase());
		int len = sbId.length();
		if (len == 15) {
			len15 = cardId;
			// 15位的，前6位行政编码后插入19
			sbId.insert(6, "19");
		} else {
			len18 = cardId;
			// 如果是18位，截掉尾一位，和年两位，即15位；
			// 红燕温馨提醒，使用过程中不应有18位转15位现象，除是对15位转18位的回算校验；
			StringBuilder cardID15 = new StringBuilder(cardId);
			cardID15.delete(17, 18);
			cardID15.delete(6, 8);
			len15 = cardID15.toString();
		}

		// 1.将身份证号码前面的17位数分别乘以不同的系数。
		// 从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
		int[] factor = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
		int sum = 0;
		for (int i = 0; i < factor.length; i++) {
			// 2.将这17位数字和系数相乘的结果相加。
			sum += Character.digit(sbId.charAt(i), 10) * factor[i];
		}
		// 3.用加出来和除以11，看余数是多少？
		int mod = sum % 11;
		// 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。
		// 余数分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。
		char[] suffix = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
		if (len == 15) {
			valid = true;
			// 如果是15位，无法校验；直接通过，同时添加校验码，形成18位
			sbId.append(suffix[mod]);
			len18 = sbId.toString();
		} else {
			valid = suffix[mod] == sbId.charAt(sbId.length() - 1);
		}
		return valid;
	}

	/**
	 * 校验是否是一个有效的身份证。如果是18的身份证，则按18位算法校验；15位无法校验。
	 */
	public static boolean valid(String id) {
		if (id == null) {
			return false;
		}
		id = id.toUpperCase();
		int xnum = 0;
		if (id.length() == 15) {
			for (int i = 0; i < id.length(); i++) {
				char ch = id.charAt(i);
				if ((ch < '0' || ch > '9') && ch != 'X') {
					return false;
				}
				if (ch == 'X') {
					if (i != id.length()-1) {
						return false;
					}
					xnum++;
				}
			}
			return xnum <= 1;
		}
		if (id.length() != 18) {
			return false;
		}
		xnum = 0;
		// 1.将身份证号码前面的17位数分别乘以不同的系数。
		// 从第一位到第十七位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
		int[] factor = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
		int sum = 0;
		for (int i = 0; i < factor.length; i++) {
			char ch = id.charAt(i);
			if ((ch < '0' || ch > '9') && ch != 'X') {
				return false;
			}
			if (ch == 'X') {
				return false;
			}
			// 2.将这17位数字和系数相乘的结果相加。
			sum += Character.digit(ch, 10) * factor[i];
		} // end for (int i = 0; i < factor.length; i++)
		if (id.charAt(id.length() - 1) == 'X') {
			xnum++;
		}
		if (xnum > 1) {
			// 超过两个x
			return false;
		}
		// 3.用加出来和除以11，看余数是多少？
		int mod = sum % 11;
		// 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。
		// 余数分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。
		char[] suffix = { '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' };
		return suffix[mod] == id.charAt(id.length() - 1);
	}

	/**
	 * 是否有效
	 */
	public boolean isValid() {
		return valid;
	}

	/**
	 * 转15位
	 */
	public String to15() {
		return len15;
	}

	/**
	 * 转18位
	 */
	public String to18() {
		return len18;
	}

	/**
	 * 性别
	 */
	public int sex() {
		return sex;
	}

	/**
	 * 生日
	 */
	public Date birth() {
		return birth;
	}

	/**
	 * 籍贯，即出生地的行政代码
	 */
	public String nativePlace() {
		return nativePlace;
	}

}
